#include "dlm.h"
#include "unistd.h"
#include "stdio.h"
#include "assert.h"
#include "pthread.h"

#define UNUSED( value )\
    (void)value;

/*
 * Test funkcji open i close
 */
void testOpen(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test1");
    if(openSt != DLM_OPEN_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }
    int closeSt = DLM_close(&dlm);
    if(closeSt != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }

    openSt = DLM_open(&dlm, "./test1.1");
    if(openSt != DLM_OPEN_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }
    closeSt = DLM_close(&dlm);
    if(closeSt != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

/*
 * Test funkcji dodającej zasób do listy zasobów procesu
 */
void testAddResource(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test2");
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 2;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }
    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

/*
 * Test funkcji dodającej zasób do listy zasobów procesu
 * w przypadku gdy zasób istnieje już na niej
 */
void testAddResourceThatExists(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test3");
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 3;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_EXISTS){
        printf("%s: error - zasób powinien być już na liście\n", __func__);
        return;
    }
    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

/*
 * Test sprawdzający czy można założyć locka EX
 * na wolnym zasobie
 */
void testTryLock(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test4");
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 4;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_trylock(&dlm, resourceId, DLM_LOCK_EX) != DLM_TRYLOCK_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }
    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

/*
 * Test sprawdzający czy można założyć locka EX
 * na zasobie z innym lockiem
 */
void testTryLockResourceWithExLock(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test5");
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 5;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_lock(&dlm, resourceId, DLM_LOCK_EX, 0) != DLM_LOCK_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    int locks[] = {DLM_LOCK_CR, DLM_LOCK_CW, DLM_LOCK_EX, DLM_LOCK_PR, DLM_LOCK_PW};
    for(unsigned int i=0; i < sizeof(locks)/sizeof(int); i++)
        if(DLM_trylock(&dlm, resourceId, locks[i]) != DLM_TRYLOCK_FAILED){
            printf("%s: error - nie można zakładać blokad %d na zasób z blokadą EX!\n", __func__, locks[i]);
            return;
        }

    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

/*
 * Test sprawdzający czy można założyć locka EX
 * dwa razy na tym samym zasobie
 */
void testLockResourceTwoTimesWithExLock(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test6");
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 6;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_lock(&dlm, resourceId, DLM_LOCK_EX, 0) != DLM_LOCK_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    const long withoutTimout = -1;
    if(DLM_lock(&dlm, resourceId, DLM_LOCK_EX, withoutTimout) != DLM_LOCK_EXISTS){
        printf("%s: error - nie można założyć blokady EX dwa razy!\n", __func__);
        return;
    }

    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

/*
 * Test sprawdzający czy zostanie zwrócony timeout
 * w przypadku gdy drugi raz będziemy chcieli założyć blokadę EX
 */
void testTimeout(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test7");
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 7;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_lock(&dlm, resourceId, DLM_LOCK_EX, 0) != DLM_LOCK_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    const long timeout2sec = 2 * 1000 * 1000 * 1000;
    if(DLM_lock(&dlm, resourceId, DLM_LOCK_EX, timeout2sec) != DLM_LOCK_TIMEOUT){
        printf("%s: error - powinien nastąpić timout\n", __func__);
        return;
    }

    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

/*
 * Test sprawdzający zablokownie, odblokowanie i ponowne zablokowanie zasobu
 */
void testUnlock(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test8");
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 8;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_lock(&dlm, resourceId, DLM_LOCK_EX, 0) != DLM_LOCK_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_unlock(&dlm, resourceId) != DLM_UNLOCK_OK){
        printf("%s: error - powinien nastąpić timout\n", __func__);
        return;
    }

    if(DLM_trylock(&dlm, resourceId, DLM_LOCK_EX) != DLM_TRYLOCK_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

/*
 * Test sprawdza reakcja na probe usuniecia niezdefiniowanego zasobu
 */
void testDelResourceThatNotExists(){
    printf("%s: init\n", __func__);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, "./test9");
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 9;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_del_resource(&dlm, resourceId) != DLM_DEL_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_del_resource(&dlm, resourceId) != DLM_DEL_RES_NOT_EXISTS){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_trylock(&dlm, resourceId, DLM_LOCK_EX) != DLM_TRYLOCK_NOT_EXISTS){
        printf("%s.%d: error!\n", __func__, __LINE__);
        return;
    }

    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}


static int counter = 0;
void* threadFunction(void *arg){
    int t = *((int*)arg);
    dlm_t dlm;
    int openSt = DLM_open(&dlm, (t==0 ? "./test9" : "./test10"));
    assert(openSt == DLM_OPEN_OK);
    const int resourceId = 9;
    if(DLM_add_resource(&dlm, resourceId) != DLM_ADD_RES_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
        pthread_exit(0);
    }

    for(int i = 0; i < 10; i++){
        if(DLM_lock(&dlm, resourceId, DLM_LOCK_EX, 0) != DLM_LOCK_OK){
            printf("%s.%d: error!\n", __func__, __LINE__);
            pthread_exit(0);
        }
        printf("%s: watek %d\n", __func__, t);
        int tmp = counter;
        usleep(10000);
        counter = tmp + (t==0? 1 : -1);
        if(DLM_unlock(&dlm, resourceId) != DLM_UNLOCK_OK){
            printf("%s.%d: error!\n", __func__, __LINE__);
            pthread_exit(0);
        }
    }
    if(DLM_close(&dlm) != DLM_CLOSE_OK){
        printf("%s.%d: error!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
    pthread_exit(0);
}

/*
 * Test tworzy dwa wątki które powinny otrzymywać dostęp do zmiennej na zmianę
 */
void testTwoThreads(){
    pthread_t f2_thread, f1_thread;

    printf("%s: init\n", __func__);
    int t1=0, t2=1;
    pthread_create(&f1_thread,NULL,&threadFunction,&t1);
    pthread_create(&f2_thread,NULL,&threadFunction,&t2);
    pthread_join(f1_thread,NULL);
    pthread_join(f2_thread,NULL);
    if(counter != 0){
        printf("%s.%d: error - licznik powinien mieć wartość 0!\n", __func__, __LINE__);
    }else{
        printf("%s: ok!\n", __func__);
    }
}

int main(int argc, char* argv[])
{
    UNUSED(argc);
    UNUSED(argv);
    testOpen();
    testAddResource();
    testAddResourceThatExists();
    testTryLock();
    testTryLockResourceWithExLock();
    testLockResourceTwoTimesWithExLock();
    testTimeout();
    testUnlock();
    testDelResourceThatNotExists();
    testTwoThreads();
    printf("Koniec\n");
    return 0;
}
